package turtle.system.invest.rule;

import java.util.Calendar;
import java.util.Date;

import turtle.system.invest.BarSeries;
import turtle.system.invest.BarType;
import turtle.system.invest.Environment;

public abstract class AbstractRule implements Rule {

    public static final int INVALID_VALUE = -1;
    public static final int NTD_PER_POINT = 2;
	public static final String KEY_RULE = "turtle.system.invest.Rule";

    protected InvestHistory history;

    protected BarSeries wtx1;
    protected BarSeries wtx0;
    private Date[] dateArray;

    private int adjustAtOpen = 0;
    private int adjustAtClose = 0;
    private int stopLoss = INVALID_VALUE;
    private long stopLossReference = 0;
    
	private long maxGain = 0;
	private long maxLoss = 0;
	
	private long highPoint = 0;
	private long lowPoint = 0;
	
	private StringBuilder messageBuilder = new StringBuilder();
	private boolean printDailyLog = true;
    
    public void setup(Environment env) {
    	env.set(KEY_RULE, this);
        wtx0 = BarSeries.getBarSeries(env, Environment.WTX0);
        wtx1 = BarSeries.getBarSeries(env, Environment.WTX1);
        history = env.getInvestHistory();
        dateArray = env.get(Environment.WTX1 + ".date");
    }

    protected void setStopLoss(int stopLoss) {
    	this.stopLoss = stopLoss == 0 ? INVALID_VALUE : stopLoss;
    }

    protected void adjustAtOpen(int amount) {
    	this.adjustAtOpen += amount;
    }

    protected void adjustAtClose(int amount) {
    	this.adjustAtClose += amount;
    }
    
    private boolean isSettlementDate(int timestamp) {
    	Calendar calendar = Calendar.getInstance();
    	calendar.setTime(dateArray[timestamp]);
    	if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.THURSDAY) {
    		int mm = calendar.get(Calendar.DAY_OF_MONTH);
    		return mm >= 16 && mm < 23;
    	}
    	return false;
    }
    
    private int adjust(int hold, int adjust, int price,	int timestamp, String message) {
    	onAdjust(hold, adjust, price);
    	if (printDailyLog) {
    		if (messageBuilder.length() > 0) messageBuilder.append(", ");
    		messageBuilder.append(String.format(
    				"%s, %s to %s @ %s", message, hold, hold + adjust, price));
    	}
    	if (hold == 0) stopLossReference = history.getDeposit();
    	history.adjustTo(hold + adjust, price, timestamp);
    	return hold + adjust;
    }
    
    protected void onAdjust(int hold, int adjust, int price) {
    }
    
    public void exercise(Environment env, int timestamp) {
        int hold = history.getHoldPosition();
        wtx1.setTimestamp(timestamp);
        wtx0.setTimestamp(timestamp);
        
        if (hold != 0 && isSettlementDate(timestamp)) {
        	hold = adjust(hold, -hold, 
        			wtx1.get(BarType.OPEN), timestamp, "settlement day");
        	if (adjustAtOpen != 0 || adjustAtClose != 0) {
        		adjustAtOpen = adjustAtClose = 0;
            }
        }

        if (adjustAtOpen != 0) {
        	hold = adjust(hold, adjustAtOpen, 
        			wtx1.get(BarType.OPEN), timestamp, "adjust at open");
        }

        if (stopLoss != INVALID_VALUE && hold != 0) {
        	int price = (int) ((stopLossReference 
        			- stopLoss - history.getDeposit()) / hold / NTD_PER_POINT);
        	if (hold > 0 && wtx1.get(BarType.LOW) < price) {
        		hold = adjust(hold, -hold, price, timestamp, "stop loss");
        	}
        	if (hold < 0 && wtx1.get(BarType.HIGH) > price) {
        		hold = adjust(hold, -hold, price, timestamp, "stop loss");
        	}
        }

        if (adjustAtClose != 0) {
        	hold = adjust(hold, adjustAtClose, 
        			wtx1.get(BarType.CLOSE), timestamp, "adjust at close");
        }
        
        updateStatistics(wtx1.get(BarType.CLOSE));
        
        if (printDailyLog) {
        	System.out.println(String.format("%d, %d, \"%s\"", 
        			hold, history.getProfit(wtx1.get(BarType.CLOSE)), 
        			messageBuilder.toString()));
        	messageBuilder = new StringBuilder();
        }

        adjustAtOpen = adjustAtClose = 0;
        doPlanning(env, timestamp);
    }

    abstract protected void doPlanning(Environment env, int timestamp);
	
    public void updateStatistics(int price) {
    	long profit = history.getProfit(price);
    	if (profit > highPoint) highPoint = profit;
    	if (profit < lowPoint) lowPoint = profit;
    	if (profit - lowPoint > maxGain) maxGain = profit - lowPoint;
    	if (highPoint - profit > maxLoss) maxLoss = highPoint - profit;
    }    
    
    public long getHighLevel() {
    	return highPoint;
    }
    
    public long getLowLevel() {
    	return lowPoint;
    }
    
    public long getMaxGain() {
    	return maxGain;
    }
    
    public long getMaxLoss() {
    	return maxLoss;
    }
}



